﻿using System.Collections.Generic;
using System.IO;
using System.Xml;
using AfterlifeLib.Lighting;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace AfterlifeLib.Levels
{
    /// <summary>
    /// The layers that are in an Afterlife level.
    /// </summary>
    public enum LevelLayer
    {
        Bottom,
        Top,
        Collision,
        Lights,
    }

    //TODO: modify this class so that it can have as much TileLayers and you want
    //and 1 CollisionLayer.
    /// <summary>
    /// The class containing all the data used in an Afterlife level.
    /// </summary>
    public class Level
    {
        #region Editor Statics
        private static string levelPath;
        /// <summary>
        /// A filepath which the editor uses to determine where the
        /// level is stored.
        /// </summary>
        public static string LevelPath
        {
            get { return levelPath; }
            set { levelPath = value; }
        }

        private static int tileWidth = 64;
        /// <summary>
        /// The width of the tiles in the tileset.
        /// </summary>
        public static int TileWidth
        {
            get { return tileWidth; }
            set { tileWidth = value; }
        }

        private static int tileHeight = 64;
        /// <summary>
        /// The height of the tiles in the tileset.
        /// </summary>
        public static int TileHeight
        {
            get { return tileHeight; }
            set { tileHeight = value; }
        }

        private static int tileDrawWidth = 64;
        /// <summary>
        /// Gets or sets the height at which we should draw the tiles.
        /// </summary>
        public static int TileDrawWidth
        {
            get { return tileDrawWidth; }
            set { tileDrawWidth = value; }
        }

        private static int tileDrawHeight = 64;
        /// <summary>
        /// Gets or sets the height at which we should draw the tiles.
        /// </summary>
        public static int TileDrawHeight
        {
            get { return tileDrawHeight; }
            set { tileDrawHeight = value; }
        }

        private static Texture2D tileset;
        /// <summary>
        /// The tileset that the editor uses.
        /// </summary>
        public static Texture2D Tileset
        {
            get { return tileset; }
            set { tileset = value; }
        }

        private static string editorTilesetPath;
        /// <summary>
        /// The filepath where the editor can find the tileset.
        /// </summary>
        public static string EditorTilesetPath
        {
            get { return editorTilesetPath; }
            set { editorTilesetPath = value; }
        }
        #endregion

        private List<TileLayer> tileLayers = new List<TileLayer>();
        public TileLayer[] TileLayers
        {
            get { return tileLayers.ToArray(); }
        }

        private CollisionLayer collisionLayer = null;
        /// <summary>
        /// The layer in which all the collision data is stored.
        /// </summary>
        public CollisionLayer CollisionLayer
        {
            get { return collisionLayer; }
            set { collisionLayer = value; }
        }

        private string gameTilesetPath;
        /// <summary>
        /// The filepath where the game can find the tileset.
        /// </summary>
        public string GameTilesetPath
        {
            get { return gameTilesetPath; }
            set { gameTilesetPath = value; }
        }

        private Color ambientColor;
        /// <summary>
        /// The ambient color of the level.
        /// </summary>
        public Color AmbientColor
        {
            get { return ambientColor; }
            set { ambientColor = value; }
        }

        /// <summary>
        /// The alpha of the ambient color of the level.
        /// </summary>
        public float AmbientAlpha
        {
            get { return (float)(AmbientColor.A) / 255; }
        }

        private List<Light> lights = new List<Light>();
        /// <summary>
        /// A List of all the light in the level.
        /// </summary>
        public List<Light> Lights
        {
            get { return lights; }
            set { lights = value; }
        }

        /// <summary>
        /// Gets the width of the level in tiles.
        /// </summary>
        public int WidthInTiles
        {
            get { return CollisionLayer.Width; }
        }

        /// <summary>
        /// Gets the height of the level in tiles.
        /// </summary>
        public int HeightInTiles
        {
            get { return CollisionLayer.Height; }
        }

        /// <summary>
        /// Gets the width of the level in pixels.
        /// </summary>
        public int WidthInPixels
        {
            get { return CollisionLayer.Width * Level.TileDrawWidth; }
        }

        /// <summary>
        /// Gets the height of the level in pixels.
        /// </summary>
        public int HeightInPixels
        {
            get { return CollisionLayer.Height * Level.TileDrawHeight; }
        }

        /// <summary>
        /// The constructor that the editor uses to create a new level.
        /// </summary>
        /// <param name="width">The width of the new level.</param>
        /// <param name="height">The height of the new level.</param>
        public Level(int width, int height)
        {
            tileLayers.Add(new TileLayer(width, height));
            collisionLayer = new CollisionLayer(width, height);
        }

        /// <summary>
        /// The constructor that is used to load a level.
        /// </summary>
        /// <param name="tileset">The tileset that the level uses</param>
        /// <param name="editorTilesetPath">The filepath which the editor uses to find the tileset.</param>
        /// <param name="gameTilesetPath">The filepath which the game uses to find the tileset.</param>
        /// <param name="bottomLayer"></param>
        /// <param name="topLayer"></param>
        /// <param name="collisionLayer"></param>
        /// <param name="ambientColor">The ambient color in this level.</param>
        /// <param name="lights">The List of Lights in this level.</param>
        public Level(Texture2D tileset, string editorTilesetPath, string gameTilesetPath, List<TileLayer> tileLayers, CollisionLayer collisionLayer, Color ambientColor, List<Light> lights)
        {
            this.tileLayers = tileLayers;
            CollisionLayer = collisionLayer;
            AmbientColor = ambientColor;
            Lights = lights;

            SetTileset(tileset, gameTilesetPath, editorTilesetPath);
        }

        #region Loading and Saving
        /// <summary>
        /// Static function used to load a Level.
        /// </summary>
        /// <param name="graphicsDevice">The GraphicsDevice needed in Texture.FromFile,
        /// null if we are loading a level in the game.</param>
        /// <param name="fileName">The filepath where the level file is located.</param>
        /// <returns>An Afterlife Level.</returns>
        public static Level FromFile(GraphicsDevice graphicsDevice, string fileName)
        {
            Level level = null;

            string editorTilesetPath = null;
            string gameTilesetPath = null;

            CollisionLayer collisionLayer = new CollisionLayer();
            List<TileLayer> tileLayers = new List<TileLayer>();

            Texture2D tileset = null;
            Color ambientColor = Color.White;
            List<Light> lights = new List<Light>();

            using (XmlReader reader = XmlReader.Create(fileName))
            {
                while (reader.Read())
                {
                    if (reader.IsStartElement())
                    {
                        if (reader.Name.Contains("TileLayer"))
                        {
                            TileLayer layer = new TileLayer();
                            layer.ReadXML(reader);
                            tileLayers.Add(layer);
                        }

                        if (reader.Name.Contains("CollisionLayer"))
                        {
                            collisionLayer.ReadXML(reader);
                        }

                        switch (reader.Name)
                        {
                            case "Tileset":
                                editorTilesetPath = reader.GetAttribute(0);
                                gameTilesetPath = reader.GetAttribute(1);

                                if(graphicsDevice != null)
                                    tileset = Texture2D.FromFile(graphicsDevice, editorTilesetPath);
                                break;
                            case "AmbientColor":
                                ambientColor = Converter.StringToColor(reader.GetAttribute(0));
                                break;
                            case "Lights":
                                int lightCount = int.Parse(reader.GetAttribute(0));

                                reader.Read();

                                //TODO:
                                //For some reason the XmlReader only reads the lights properly into a list
                                //if we loop lightCount*2 times.
                                for (int i = 0; i < lightCount * 2; i++)
                                {
                                    reader.Read();

                                    if (reader.HasAttributes)
                                        lights.Add(new Light(
                                            reader.GetAttribute(0),
                                            Converter.StringToColor(reader.GetAttribute(1)),
                                            float.Parse(reader.GetAttribute(2)),
                                            float.Parse(reader.GetAttribute(3)),
                                            Converter.StringToVector3(reader.GetAttribute(4))
                                        ));
                                }
                                break;
                        }
                    }
                }
            }

            level = new Level(tileset, editorTilesetPath, gameTilesetPath, tileLayers, collisionLayer, ambientColor, lights);

            return level;
        }

        /// <summary>
        /// Function used to save a level from within the level editor.
        /// </summary>
        /// <param name="fileName">Where the level needs to be saved.</param>
        /// <param name="gameTilesetPath">The filepath used to load the tileset from within the game.</param>
        public void Save(string fileName, string gameTilesetPath)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;

            using (XmlWriter writer = XmlWriter.Create(fileName, settings))
            {
                writer.WriteStartDocument();

                //<Level>
                writer.WriteStartElement("Level");

                //<TileSet File="">
                writer.WriteStartElement("Tileset");
                writer.WriteAttributeString("EditorPath", Level.EditorTilesetPath);
                writer.WriteAttributeString("GamePath", gameTilesetPath + Path.GetFileNameWithoutExtension(Level.EditorTilesetPath));
                writer.WriteEndElement();

                for (int i = 0; i < tileLayers.Count; i++)
                {
                    tileLayers[i].WriteXML(writer, i, LayerType.Tile);
                }
                CollisionLayer.WriteXML(writer, 0, LayerType.Collision);

                //<AmbientColor R="" G="" B="" />
                writer.WriteStartElement("AmbientColor");
                writer.WriteAttributeString("Color", Converter.ColorToString(AmbientColor));
                writer.WriteEndElement();

                //<Lights>
                writer.WriteStartElement("Lights");
                writer.WriteAttributeString("Amount", Lights.Count.ToString());
                foreach (Light light in Lights)
                {
                    writer.WriteStartElement("Light");
                    writer.WriteAttributeString("Name", light.Name);
                    writer.WriteAttributeString("Color", Converter.ColorToString(light.Color));
                    writer.WriteAttributeString("Radius", light.Radius.ToString());
                    writer.WriteAttributeString("Strength", light.Strength.ToString());
                    writer.WriteAttributeString("Position", Converter.Vector3ToString(light.Position));
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();

                writer.WriteEndElement();

                writer.WriteEndDocument();
            }
        }
        #endregion

        /// <summary>
        /// Method used to set the tileset for all layers and the paths 
        /// where the tileset can be found.
        /// </summary>
        /// <param name="tileset">The tileset.</param>
        /// <param name="gameTilesetPath">The filepath where the game can find the tileset.</param>
        /// <param name="editorTilesetPath">The filepath where the editor can find the tileset.</param>
        public void SetTileset(Texture2D tileset, string gameTilesetPath, string editorTilesetPath)
        {
            GameTilesetPath = gameTilesetPath;
            EditorTilesetPath = editorTilesetPath;

            if (tileset != null)
            {
                foreach (var layer in tileLayers)
                {
                    layer.Tileset = tileset;
                }
                Level.Tileset = tileset;
            }

            Level.EditorTilesetPath = editorTilesetPath;
        }

        /// <summary>
        /// Method used to resize a level in the editor.
        /// </summary>
        /// <param name="newWidth">The new width of the level.</param>
        /// <param name="newHeight">The new height of the level.</param>
        public void Resize(int newWidth, int newHeight, int newValue)
        {
            foreach (var layer in tileLayers)
            {
                layer.Resize(newWidth, newHeight, newValue);
            }
            CollisionLayer.Resize(newWidth, newHeight, newValue);
        }

        public void ResizeOffset(int offsetX, int offsetY, int newValue)
        {
            Resize(WidthInTiles + offsetX, HeightInTiles + offsetY, newValue); 
        }

        /// <summary>
        /// Draws the specified layer to the screen.
        /// </summary>
        /// <param name="spriteBatch">SpriteBatch used to draw the layer.</param>
        /// <param name="camera">The camera used to draw the layer at the right position.</param>
        /// <param name="layer">Which layer to draw.</param>
        public void DrawLayer(SpriteBatch spriteBatch, Camera2D camera, int layerIndex)
        {
            if (layerIndex < 0 || layerIndex >= tileLayers.Count)
                return;

            spriteBatch.Begin();
            {
                tileLayers[layerIndex].Draw(spriteBatch, camera);
            }
            spriteBatch.End();
        }

        /// <summary>
        /// Draws the specified layer to the screen.
        /// </summary>
        /// <param name="spriteBatch">SpriteBatch used to draw the layer.</param>
        /// <param name="camera">The camera used to draw the layer at the right position.</param>
        /// <param name="layer">Which layer to draw.</param>
        /// <param name="tileReplacement">A Texture2D used to draw if there is no tile to be drawn.</param>
        /// <param name="drawGrid">Wether we should draw a grid if theere is no tile.</param>
        public void DrawEditorLayer(SpriteBatch spriteBatch, Camera2D camera, Texture2D tileReplacement, bool drawGrid, int layerIndex)
        {
            if (layerIndex < 0 || layerIndex >= tileLayers.Count)
                return;

            spriteBatch.Begin();
            {
                tileLayers[layerIndex].Draw(spriteBatch, camera);
            }
            spriteBatch.End();
        }
    }
}
